package cn.sylinx.hbatis.ext.starter.springboot;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.sql.DataSource;

import cn.sylinx.hbatis.db.dialect.DbType;
import cn.sylinx.hbatis.ds.DataSourceWrapper;
import cn.sylinx.hbatis.exception.HbatisException;
import cn.sylinx.hbatis.ext.mirage.plugin.SqlResourcePreloadPlugin;
import cn.sylinx.hbatis.ext.starter.pool.DataSourceWrapperFactory;
import cn.sylinx.hbatis.ext.starter.springboot.config.CacheProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.DatasourcePoolProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.DefaultDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ExistDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.HbatisProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ModelpreloadProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.MultiDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.ProxyProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.SingleDatasourceProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.SqlResourcePreloadProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.StatementHandlerProperties;
import cn.sylinx.hbatis.ext.starter.springboot.config.TransactionProperties;
import cn.sylinx.hbatis.kit.ClassUtil;
import cn.sylinx.hbatis.kit.StrKit;
import cn.sylinx.hbatis.log.GLog;
import cn.sylinx.hbatis.plugin.IPlugin;
import cn.sylinx.hbatis.plugin.cache.GuavaCacheConfig;
import cn.sylinx.hbatis.plugin.cache.GuavaCachePlugin;
import cn.sylinx.hbatis.plugin.datasource.DataSourcePlugin;
import cn.sylinx.hbatis.plugin.debug.DebugPlugin;
import cn.sylinx.hbatis.plugin.model.ModelPreloadPlugin;
import cn.sylinx.hbatis.plugin.proxy.ProxyPlugin;
import cn.sylinx.hbatis.plugin.statement.StatementHandlerPlugin;
import cn.sylinx.hbatis.plugin.transaction.TransactionIsolationPlugin;

class PluginBuilder {

	private List<IPlugin> pluginList = new ArrayList<IPlugin>();
	private HbatisProperties hbatisProperties;
	private DataSource dataSource;

	public PluginBuilder(HbatisProperties hbatisProperties, DataSource dataSource) {
		this.hbatisProperties = hbatisProperties;
		this.dataSource = dataSource;
		check();
	}

	private void check() {
		if (hbatisProperties == null) {
			throw new HbatisException("hbatis配置文件为空");
		}

		ExistDatasourceProperties edp = hbatisProperties.getExistDatasourceProperties();
		if (edp != null && edp.isExist()) {
			// 现有数据源已存在，需判断当时datasource是否空

			if (dataSource == null) {
				throw new HbatisException("现有数据源为空值...");
			}
		}
	}

	/**
	 * 初始化modelpreload插件
	 * 
	 * @param env
	 */
	public PluginBuilder initModelPreLoadPlugin() {

		ModelpreloadProperties modelpreloadProperties = hbatisProperties.getModelpreloadProperties();

		boolean inited = modelpreloadProperties.isInited();
		if (!inited) {
			return this;
		}

		GLog.debug("ModelPreloadPlugin inited.");
		ModelPreloadPlugin mp = new ModelPreloadPlugin();

		String acmStrategy = modelpreloadProperties.getAcmStrategy();
		if (StrKit.isNotBlank(acmStrategy)) {
			// 设置全局模型字段映射策略
			mp.setAcmStrategyClass(acmStrategy);
		}

		String scanPackageList = modelpreloadProperties.getScanPackageList();
		if (StrKit.isNotBlank(scanPackageList)) {
			GLog.debug("ModelPreloadPlugin scanPackageList:{}", scanPackageList);
			String[] pkgArray = scanPackageList.split(",");
			mp.setScanPackageList(Arrays.asList(pkgArray));
		}

		pluginList.add(mp);
		return this;
	}

	public PluginBuilder initSqlResourcePreloadPlugin() {

		SqlResourcePreloadProperties srpp = hbatisProperties.getSqlResourcePreloadProperties();

		String path = srpp.getSqlpath();
		if (StrKit.isBlank(path)) {
			return this;
		}

		GLog.debug("SqlResourcePreloadPlugin inited.");
		String[] pathArray = path.split(",");
		List<String> pathList = Arrays.asList(pathArray);
		SqlResourcePreloadPlugin plugin = new SqlResourcePreloadPlugin(pathList);
		pluginList.add(plugin);
		return this;
	}

	/**
	 * 添加sql语句处理插件
	 * 
	 * @param p
	 */
	public PluginBuilder initStatementHandlerPlugin() {

		StatementHandlerProperties shpp = hbatisProperties.getStatementHandlerProperties();
		String statementHandlerClass = shpp.getClazz();
		if (StrKit.isBlank(statementHandlerClass)) {
			return this;
		}

		GLog.debug("StatementHandlerPlugin inited.");
		StatementHandlerPlugin shp = new StatementHandlerPlugin();
		shp.setStatementHandlerClass(statementHandlerClass);
		pluginList.add(shp);
		return this;
	}

	public PluginBuilder initTransactionPlugin() {

		TransactionProperties tp = hbatisProperties.getTransactionProperties();
		GLog.debug("TransactionIsolationPlugin inited.");
		int levelInt = tp.getLevel();
		TransactionIsolationPlugin plugin = new TransactionIsolationPlugin(levelInt);
		pluginList.add(plugin);
		return this;
	}

	public PluginBuilder initDebugPlugin() {
		DebugPlugin debugPlugin = new DebugPlugin();
		debugPlugin.setDebug(hbatisProperties.isDebug());
		pluginList.add(debugPlugin);
		return this;
	}

	public PluginBuilder initCachePlugin() {

		CacheProperties cp = hbatisProperties.getCacheProperties();
		boolean useCache = cp.isInited();
		if (!useCache) {
			return this;
		}

		boolean isClassExist = ClassUtil.isClassExist("com.google.common.cache.CacheBuilder");
		if (!isClassExist) {
			GLog.error("guava依赖库没有加载，缓存插件无法初始化");
			return this;
		}

		// hbatis.plugin.cache.expire=5, 过期时间，分钟
		// hbatis.plugin.cache.maxSize=2000, 最大个数缓存键
		long expireLong = cp.getExpire();
		long maxSizeLong = cp.getMaxSize();

		GuavaCacheConfig gcc = new GuavaCacheConfig();
		gcc.setExpireAfterWrite(expireLong);
		gcc.setMaximumSize(maxSizeLong);

		GLog.debug("cache config, expireAfterWrite:{}, maximumSize:{}", expireLong, maxSizeLong);
		pluginList.add(new GuavaCachePlugin(gcc));
		return this;
	}

	// 初始化代理插件
	public PluginBuilder initProxyPlugin() {

		// TransactionIsolationPlugin
		ProxyProperties pp = hbatisProperties.getProxyProperties();
		String clzList = pp.getClazz();
		if (StrKit.isBlank(clzList)) {
			return this;
		}

		ProxyPlugin plugin = new ProxyPlugin();
		String[] clzArray = clzList.split(",");
		for (String clz : clzArray) {
			if (StrKit.isNotBlank(clz)) {
				IPlugin item = null;
				try {
					item = (IPlugin) Class.forName(clz.trim()).newInstance();
				} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
					GLog.error("初始化插件失败, clz: " + clz, e);
				}
				if (item != null) {
					// 加入代理插件
					plugin.addPlugin(item);
				}
			}
		}

		pluginList.add(plugin);
		return this;
	}

	private DataSourceWrapper buildDataSource(Map<String, Object> dsMap, Map<String, Object> extConfig) {
		return DataSourceWrapperFactory.buildDataSource(dsMap, extConfig);
	}

	/**
	 * 初始化数据源plugin
	 * 
	 * @param env
	 */
	public PluginBuilder initDataSourcePlugin() {

		////// 读取主数据源开始
		DataSourcePlugin p = new DataSourcePlugin();
		DataSourceWrapper defaultDataSource = null;
		if (dataSource != null) {
			// 主数据源使用现有注入数据源
			ExistDatasourceProperties edp = hbatisProperties.getExistDatasourceProperties();
			String dbtype = edp.getDbtype();
			defaultDataSource = DataSourceWrapper.create(dataSource, DbType.getDbType(dbtype));

		} else {

			DefaultDatasourceProperties ddp = hbatisProperties.getDefaultDatasource();

			Map<String, Object> dsMap = new HashMap<String, Object>();
			dsMap.put("url", ddp.getUrl());
			dsMap.put("driver", ddp.getDriver());
			dsMap.put("username", ddp.getUsername());
			dsMap.put("password", ddp.getPassword());
			dsMap.put("dbtype", ddp.getDbtype());
			dsMap.put("pooltype", ddp.getPooltype());

			// 获取其它配置
			DatasourcePoolProperties extConfig = ddp.getConfig();
			defaultDataSource = buildDataSource(dsMap, extConfig);
		}
		// 添加默认数据源
		p.setDataSourceWrapper(defaultDataSource);
		pluginList.add(p);
		/////// 主数据源初始化完毕 ////////

		////// 以下是多数据源初始化 //////////
		// 读取配置文件获取更多数据源，也可以通过defaultDataSource读取数据库获取更多数据源
		MultiDatasourceProperties mdp = hbatisProperties.getMultiDatasource();
		if (mdp == null || mdp.isEmpty()) {
			return this;
		}

		Set<Entry<String, SingleDatasourceProperties>> entrySets = mdp.entrySet();

		for (Entry<String, SingleDatasourceProperties> entry : entrySets) {// 多个数据源

			SingleDatasourceProperties entrySdp = entry.getValue();

			Map<String, Object> dsMapMulti = new HashMap<String, Object>();
			dsMapMulti.put("url", entrySdp.getUrl());
			dsMapMulti.put("driver", entrySdp.getDriver());
			dsMapMulti.put("username", entrySdp.getUsername());
			dsMapMulti.put("password", entrySdp.getPassword());
			dsMapMulti.put("dbtype", entrySdp.getDbtype());
			dsMapMulti.put("pooltype", entrySdp.getPooltype());

			Map<String, Object> extConfigMulti = entry.getValue().getConfig();

			DataSourceWrapper ds = buildDataSource(dsMapMulti, extConfigMulti);
			DataSourcePlugin ptmp = new DataSourcePlugin();
			ptmp.setJdbcResourceName(entry.getKey());
			ptmp.setDataSourceWrapper(ds);
			pluginList.add(ptmp);

		}
		////// 多数据源初始化完毕 //////////

		return this;
	}

	public List<IPlugin> build() {
		return this.pluginList;
	}
}
